// Copyright DustedPixels.com 2008. All rights reserved.

package com.dustedpixels.jasmin.unit.compiler;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

/**
 * @author micapolos@gmail.com (Michal Pociecha-Los)
 */
public final class UnitCompilerImpl implements UnitCompiler {
  public Unit compile(Unit unit, String compiledUnitName, UnitLoader unitLoader)  throws Exception {
    // Compile internal units
    Map<String, Unit> compiledUnits = new HashMap<String, Unit>();
    for (ConstructorLine constructorLine : unit.constructorLines) {
      if (constructorLine.type == ConstructorLine.Type.NEW_UNIT_CALL) {
        NewUnitCall newUnitCall = constructorLine.newUnitCall;
        
        System.out.println("Loading: " + newUnitCall.qualifiedUnitName);
        Unit rawUnit = unitLoader.loadUnit(newUnitCall.qualifiedUnitName);
        
        System.out.println("Compiling: " + rawUnit.qualifiedName);
        Unit compiledUnit = compile(rawUnit, newUnitCall.qualifiedUnitName, unitLoader);
        
        compiledUnits.put(newUnitCall.fieldName, compiledUnit);
      }
    }
    
    // Merge imports
    Set<String> compiledImports = new TreeSet<String>();
    for (String anImport : unit.imports) {
      compiledImports.add(anImport);
    }
    for (Unit compiledUnit : compiledUnits.values()) {
      for (String compiledImport : compiledUnit.imports) {
        compiledImports.add(compiledImport);
      }
      compiledImports.add(compiledUnit.packageName + ".*");
    }

    Set<String> fieldNames = new HashSet<String>();
    for (FieldLine fieldLine : unit.fieldLines) fieldNames.add(fieldLine.name);
    
    // Compile fields code.
    List<FieldLine> compiledFieldLines = new ArrayList<FieldLine>();
    
    // Copy all field lines from source unit except fields containing internal units.
    for (FieldLine fieldLine : unit.fieldLines) {
      // Filter out internal unit fields
      if (!compiledUnits.containsKey(fieldLine.name)) {
        compiledFieldLines.add(fieldLine);
      }
    }
    
    // Copy all field lines from internal units appending prefix.
    for (Map.Entry<String, Unit> entry : compiledUnits.entrySet()) {
      String unitFieldName = entry.getKey();
      Unit compiledUnit = entry.getValue();
      for (FieldLine fieldLine : compiledUnit.fieldLines) {
        compiledFieldLines.add(fieldLine.compiled(unitFieldName));
      }
    }
    
    // Copy constructor lines from source unit replacing new unit calls with
    // constructor lines of new unit.
    List<ConstructorLine> compiledConstructorLines = new ArrayList<ConstructorLine>();
    for (ConstructorLine constructorLine : unit.constructorLines) {
      if (constructorLine.type == ConstructorLine.Type.NEW_UNIT_CALL) {
        String fieldName = constructorLine.newUnitCall.fieldName;
        Unit compiledUnit = compiledUnits.get(fieldName);
        for (ConstructorLine compiledConstructorLine : compiledUnit.constructorLines) {
          compiledConstructorLines.add(compiledConstructorLine.replaceLocalFieldAccess(compiledUnit, constructorLine.newUnitCall));
        }
      } else {
        compiledConstructorLines.add(constructorLine);
      }
    }
    
    // Copy updateEndPoints method lines from source unit replacing
    // calls for internal units with the code.
    List<UpdateEndPointsLine> compiledUpdateEndPointsLines = new ArrayList<UpdateEndPointsLine>();
    for (UpdateEndPointsLine updateEndPointsLine : unit.updateEndPointsLines) {
      if (updateEndPointsLine.type == UpdateEndPointsLine.Type.UPDATE_END_POINTS_CALL) {
        String target = updateEndPointsLine.updateEndPointsTarget;
        Unit compiledUnit = compiledUnits.get(target);
        for (UpdateEndPointsLine compiledUpdateEndPointsLine : compiledUnit.updateEndPointsLines) {
          compiledUpdateEndPointsLines
              .add(compiledUpdateEndPointsLine.replaceLocalFieldAccess(compiledUnit, target));
        }
      } else {
        compiledUpdateEndPointsLines.add(updateEndPointsLine.replacedUnitFieldAccess(unit));
      }
    }
    
    Unit compiledUnit = new Unit(compiledUnitName);
    compiledUnit.imports = compiledImports.toArray(new String[0]);
    compiledUnit.fieldLines = compiledFieldLines.toArray(new FieldLine[0]);
    compiledUnit.constructorLines = compiledConstructorLines.toArray(new ConstructorLine[0]);
    compiledUnit.constructorArgs = unit.constructorArgs;
    compiledUnit.updateEndPointsLines = compiledUpdateEndPointsLines.toArray(new UpdateEndPointsLine[0]);
    compiledUnit.unitFieldNames = Collections.emptySet();
    
    return compiledUnit;
  }
}
